package cc.rengu.igas.channel.xmup.common.util;

import cc.rengu.igas.channel.xmup.common.constant.XmupParamConstant;
import cc.rengu.igas.channel.xmup.common.constant.XmupTreeNodeConstant;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * 银联全渠道加签验签工具类
 * Created by 王成 on 2018/12/26.
 */
public class XmupSecurityUtil {
    /* 算法常量： SHA1 */
    private static final String ALGORITHM_SHA1 = "SHA-1";
    /* 算法常量： SHA256 */
    private static final String ALGORITHM_SHA256 = "SHA-256";
    /* 算法常量：SHA1withRSA */
    private static final String BC_PROV_ALGORITHM_SHA1RSA = "SHA1withRSA";
    /* 算法常量：SHA256withRSA */
    private static final String BC_PROV_ALGORITHM_SHA256RSA = "SHA256withRSA";

    public static BASE64Decoder decBase64 = new BASE64Decoder();
    public static BASE64Encoder encase64 = new BASE64Encoder();

    /**
     * 数字签名
     *
     * @param signData  待签名字符串
     * @param secureKey 签名密钥
     * @return 带签名的字符串
     * @throws Exception 异常
     */
    public static String sign(String signData, String secureKey) throws Exception {
        if (null == signData || signData.isEmpty() || null == secureKey || secureKey.isEmpty()) {
            /* 签名密钥为空或者待签名数据为空，直接返回签名失败 */
            return signData;
        }

        Map<String, String> signDataMap = convertResultString2Map(signData);
        signDataMap = filterBlank(signDataMap);

        String encoding = signDataMap.get(XmupTreeNodeConstant.encoding);
        encoding = (null == encoding || encoding.isEmpty()) ? XmupParamConstant.UTF_8_ENCODING : encoding;

        /*处理商户号和商户级别*/
        String merchantId = signDataMap.get(XmupTreeNodeConstant.merchantId);
        String merLevel = signDataMap.get(XmupTreeNodeConstant.merLevel);
        String first = new String(Base64.Base64Encode((merchantId + "|" + merLevel).getBytes(encoding)));

        String signMethod = signDataMap.get(XmupTreeNodeConstant.signMethod);
        if (null == signMethod || signMethod.isEmpty()) {
            /* 签名方法为空，直接返回失败 */
            return signData;
        }

        /* 生成签名 */
        return getRequestParamString(signDataMap, encoding);
    }

    public static String sign(String signData, PrivateKey secureKey, PublicKey publicKey) throws Exception {
        if (null == signData || signData.isEmpty() || null == secureKey) {
            /* 签名密钥为空或者待签名数据为空，直接返回签名失败 */
            return signData;
        }
        Map<String, String> signDataMap = convertResultString2Map(signData);
        signDataMap = filterBlank(signDataMap);
        String signMethod = signDataMap.get(XmupTreeNodeConstant.signMethod);
        if (null == signMethod || signMethod.isEmpty()) {
            /* 签名方法为空，直接返回失败 */
            return signData;
        }

        String encoding = signDataMap.get(XmupTreeNodeConstant.encoding);
        encoding = (null == encoding || encoding.isEmpty()) ? XmupParamConstant.UTF_8_ENCODING : encoding;


        /*处理商户号和商户级别*/
        String merchantId = signDataMap.get(XmupTreeNodeConstant.merchantId);
        String merLevel = signDataMap.get(XmupTreeNodeConstant.merLevel);
        String first = encase64.encodeBuffer((merchantId + "|" + merLevel).getBytes());


        /*处理BASE64(RSA(报文加密密钥))*/
        String StrpublicKey = new String(Base64.Base64Encode(publicKey.getEncoded()));
        byte[] RSASecond = RSAUtils.encryptByPublicKey(MD5.getHashString(XmupTreeNodeConstant.xmjfptHashString).getBytes(encoding), StrpublicKey);
        String second = Base64.Base64Encode(RSASecond);

        /*BASE64(3DES(报文原文))*/
        String third = encase64.encodeBuffer(DesUtil.encrypt(signData.getBytes(encoding), MD5.getHashString(XmupTreeNodeConstant.xmjfptHashString).getBytes(encoding)));

        /*BASE64(RSA(MD5(报文原文)))*/
        String MD5fourth = MD5.getHashString(signData.getBytes(encoding));
        String privateKey = Base64Utils.encode(secureKey.getEncoded());
        String fourth = Base64.Base64Encode(RSAUtils.encryptByPrivateKey(MD5fourth.getBytes(encoding), privateKey));

        /* 生成签名 */
        String reqStr = (first + "|" + second + "|" + third + "|" + fourth).replaceAll("\r|\n", "");
        return reqStr;
    }

    /**
     * 验证签名
     *
     * @param signData  待签名原始数据
     * @param secureKey 验签密钥
     * @return true-成功，false-失败
     * @throws Exception 异常
     */
    public static boolean verifySign(String signData, String secureKey) throws Exception {
        if (null == signData || signData.isEmpty() || null == secureKey || secureKey.isEmpty()) {
            /* 签名密钥为空或者待签名数据为空，直接返回签名失败 */
            return false;
        }


        Map<String, String> signDataMap = convertResultString2Map(signData);
        String signMethod = signDataMap.get(XmupTreeNodeConstant.signMethod);
        String signature = signDataMap.get(XmupTreeNodeConstant.signature);
        if (null == signMethod || signMethod.isEmpty() || null == signature || signature.isEmpty()) {
            /* 签名方法或者签名域为空，直接返回失败 */
            return false;
        }
        String encoding = signDataMap.get(XmupTreeNodeConstant.encoding);
        encoding = (null == encoding || encoding.isEmpty()) ? XmupParamConstant.UTF_8_ENCODING : encoding;
        return verifySignBySecureKey(signDataMap, secureKey, encoding);
    }

    /**
     * 使用验签密钥对签名进行验签
     *
     * @param signDataMap 待验签数据
     * @param secureKey   验签密钥
     * @param encoding    编码方式
     * @return true-成功，false-失败
     * @throws Exception 异常
     */
    private static boolean verifySignBySecureKey(Map<String, String> signDataMap, String secureKey, String encoding) throws Exception {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }

        return false;
    }


    /**
     * 删除空标签字段
     *
     * @param contentData 待过滤的MAP对象
     * @return 过滤后的MAP对象
     */
    private static Map<String, String> filterBlank(Map<String, String> contentData) {
        Map<String, String> submitFromData = new HashMap<String, String>();
        Set<String> keyset = contentData.keySet();
        for (String key : keyset) {
            String value = contentData.get(key);
            if (value != null && !"".equals(value.trim())) {
                /* 对value值进行去除前后空处理 */
                submitFromData.put(key, value.trim());
            }
        }
        return submitFromData;
    }

    /**
     * 将报文字符串转换成MAP对象
     *
     * @param result 报文字符串
     * @return MAP对象
     */
    public static Map<String, String> convertResultString2Map(String result) {
        Map<String, String> map = null;
        if (result != null && !XmupParamConstant.BLANK.equals(result.trim())) {
            if (result.startsWith(XmupParamConstant.LEFT_BRACE) && result.endsWith(XmupParamConstant.RIGHT_BRACE)) {
                result = result.substring(1, result.length() - 1);
            }
            map = parseJsonString(result);
        }
        return map;
    }

    /**
     * 将MAP对象转换成带&分隔符的JSON字符串
     *
     * @param dataMap MAP对象
     * @return 字符串
     */
    public static String covertResultMap2String(Map<String, String> dataMap) {
        TreeMap tree = new TreeMap();
        Iterator it = dataMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry sf = (Map.Entry) it.next();
            tree.put(sf.getKey(), sf.getValue());
        }
        it = tree.entrySet().iterator();
        StringBuffer sf1 = new StringBuffer();
        while (it.hasNext()) {
            Map.Entry en = (Map.Entry) it.next();
            sf1.append(en.getKey() + XmupParamConstant.EQUAL + en.getValue() + XmupParamConstant.AMPERSAND);
        }
        return sf1.substring(0, sf1.length() - 1);
    }

    /**
     * 将MAP对象转换成带&分隔符的JSON字符串
     *
     * @param data MAP对象
     * @return JSON字符串
     */
    private static String covertMap2String(Map<String, String> data) {
        TreeMap<String, String> tree = new TreeMap<String, String>();
        Iterator<Map.Entry<String, String>> it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            if (XmupTreeNodeConstant.signature.equals(en.getKey().trim())) {
                continue;
            }
            tree.put(en.getKey(), en.getValue());
        }
        it = tree.entrySet().iterator();
        StringBuffer sf = new StringBuffer();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            sf.append(en.getKey() + XmupParamConstant.EQUAL + en.getValue() + XmupParamConstant.AMPERSAND);
        }
        return sf.substring(0, sf.length() - 1);
    }

    /**
     * 将还分隔符&的JSON字符串转换成Map对象
     *
     * @param jsonString JSON字符串
     * @return MAP对象
     */
    public static Map<String, String> parseJsonString(String jsonString) {
        Map<String, String> map = new HashMap<String, String>();
        int len = jsonString.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        if (len > 0) {
            for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
                curChar = jsonString.charAt(i);// 取当前字符
                if (isKey) {// 如果当前生成的是key
                    if (curChar == '=') {// 如果读取到=分隔符
                        key = temp.toString();
                        temp.setLength(0);
                        isKey = false;
                    } else {
                        temp.append(curChar);
                    }
                } else {// 如果当前生成的是value
                    if (isOpen) {
                        if (curChar == openName) {
                            isOpen = false;
                        }
                    } else {//如果没开启嵌套
                        if (curChar == '{') {//如果碰到，就开启嵌套
                            isOpen = true;
                            openName = '}';
                        }
                        if (curChar == '[') {
                            isOpen = true;
                            openName = ']';
                        }
                    }
                    if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                        putKeyValueToMap(temp, isKey, key, map);
                        temp.setLength(0);
                        isKey = true;
                    } else {
                        temp.append(curChar);
                    }
                }
            }
            putKeyValueToMap(temp, isKey, key, map);
        }
        return map;
    }

    /**
     * 将还分隔符&的kv字符串转换成Map对象
     *
     * @param kvString
     * @return
     */
    public static Map<String, Object> parseKvString(String kvString) {
        Map<String, Object> map = new HashMap<String, Object>();
        int len = kvString.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        if (len > 0) {
            for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
                curChar = kvString.charAt(i);// 取当前字符
                if (isKey) {// 如果当前生成的是key
                    if (curChar == '=') {// 如果读取到=分隔符
                        key = temp.toString();
                        temp.setLength(0);
                        isKey = false;
                    } else {
                        temp.append(curChar);
                    }
                } else {// 如果当前生成的是value
                    if (isOpen) {
                        if (curChar == openName) {
                            isOpen = false;
                        }
                    } else {//如果没开启嵌套
                        if (curChar == '{') {//如果碰到，就开启嵌套
                            isOpen = true;
                            openName = '}';
                        }
                        if (curChar == '[') {
                            isOpen = true;
                            openName = ']';
                        }
                    }
                    if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                        putKeyValueToMapObj(temp, isKey, key, map);
                        temp.setLength(0);
                        isKey = true;
                    } else {
                        temp.append(curChar);
                    }
                }
            }
            putKeyValueToMapObj(temp, isKey, key, map);
        }
        return map;
    }

    /**
     * 将KEY及VALUE值设置对Map对象
     *
     * @param temp  VALUE值
     * @param isKey 是否为KEY标识
     * @param key   KEY值
     * @param map   MAP对象
     */
    private static void putKeyValueToMap(StringBuilder temp, boolean isKey, String key, Map<String, String> map) {
        if (isKey) {
            key = temp.toString();
            if (key.length() == 0) {
                throw new RuntimeException("String format illegal");
            }
            map.put(key, "");
        } else {
            if (key.length() == 0) {
                throw new RuntimeException("String format illegal");
            }
            map.put(key, temp.toString());
        }
    }

    /**
     * 将KEY及VALUE值设置对Map对象
     *
     * @param temp
     * @param isKey
     * @param key
     * @param map
     */
    private static void putKeyValueToMapObj(StringBuilder temp, boolean isKey, String key, Map<String, Object> map) {
        if (isKey) {
            key = temp.toString();
            if (key.length() == 0) {
                throw new RuntimeException("String format illegal");
            }
            map.put(key, "");
        } else {
            if (key.length() == 0) {
                throw new RuntimeException("String format illegal");
            }
            map.put(key, temp.toString());
        }
    }

    /**
     * 账号处理
     *
     * @param acctNo 账号
     * @return 账号
     */
    private static byte[] formatPan(String acctNo) {
        int tPanLen = acctNo.length();
        byte[] tByte = new byte[8];
        int temp = tPanLen - 13;
        tByte[0] = (byte) 0x00;
        tByte[1] = (byte) 0x00;
        for (int i = 2; i < 8; i++) {
            String a = acctNo.substring(temp, temp + 2);
            tByte[i] = (byte) Integer.parseInt(a, 16);
            temp = temp + 2;
        }
        return tByte;
    }

    /**
     * 将密码转换成PINBLOCK
     *
     * @param pinData 密码
     * @return PINGBLOCK
     */
    private static byte[] pinToPinBlock(String pinData) {
        int tTemp = 1;
        int tPinLen = pinData.length();
        byte[] tByte = new byte[8];
        tByte[0] = (byte) Integer.parseInt(Integer.toString(tPinLen), 10);
        if (tPinLen % 2 == 0) {
            for (int i = 0; i < tPinLen; ) {
                String a = pinData.substring(i, i + 2);
                tByte[tTemp] = (byte) Integer.parseInt(a, 16);
                if (i == (tPinLen - 2)) {
                    if (tTemp < 7) {
                        for (int x = (tTemp + 1); x < 8; x++) {
                            tByte[x] = (byte) 0xff;
                        }
                    }
                }
                tTemp++;
                i = i + 2;
            }
        } else {
            for (int i = 0; i < tPinLen - 1; ) {
                String a;
                a = pinData.substring(i, i + 2);
                tByte[tTemp] = (byte) Integer.parseInt(a, 16);
                if (i == (tPinLen - 3)) {
                    String b = pinData.substring(tPinLen - 1) + "F";
                    tByte[tTemp + 1] = (byte) Integer.parseInt(b, 16);
                    if ((tTemp + 1) < 7) {
                        for (int x = (tTemp + 2); x < 8; x++) {
                            tByte[x] = (byte) 0xff;
                        }
                    }
                }
                tTemp++;
                i = i + 2;
            }
        }
        return tByte;
    }

    /**
     * 对字符串进行SHA256计算并转换成16进制编码
     *
     * @param data     待计算的字符串
     * @param encoding 编码方式
     * @return 转换后的字符串
     */
    public static String sha256X16Str(String data, String encoding) throws Exception {
        MessageDigest md = MessageDigest.getInstance(ALGORITHM_SHA256);
        md.reset();
        md.update(data.getBytes(encoding));
        byte[] bytes = md.digest();
        StringBuilder sha256StrBuff = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
                sha256StrBuff.append("0").append(Integer.toHexString(0xFF & bytes[i]));
            } else {
                sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
            }
        }
        return sha256StrBuff.toString();
    }

    /**
     * 对字符串进行SM3计算并转换成16进制编码
     *
     * @param data     待计算的字符串
     * @param encoding 编码方式
     * @return 转换后的字符串
     */
    public static String sm3X16Str(String data, String encoding) throws Exception {
        SM3Digest sm3 = new SM3Digest();
        sm3.update(data.getBytes(encoding), 0, data.getBytes(encoding).length);
        byte[] bytes = new byte[sm3.getDigestSize()];
        sm3.doFinal(bytes, 0);
        StringBuilder sm3StrBuff = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
                sm3StrBuff.append("0").append(Integer.toHexString(0xFF & bytes[i]));
            } else {
                sm3StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
            }
        }
        return sm3StrBuff.toString();
    }

    /**
     * 对字符串进行SHA1计算并转换成16进制编码
     *
     * @param data     待计算的字符串
     * @param encoding 编码方式
     * @return 转换后的字符串
     */
    public static byte[] sha1X16(String data, String encoding) throws Exception {
        MessageDigest md = MessageDigest.getInstance(ALGORITHM_SHA1);
        md.reset();
        md.update(data.getBytes(encoding));
        byte[] bytes = md.digest();
        StringBuilder sha1StrBuff = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
                sha1StrBuff.append("0").append(Integer.toHexString(0xFF & bytes[i]));
            } else {
                sha1StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
            }
        }
        return sha1StrBuff.toString().getBytes(encoding);
    }

    /**
     * 对字符串进行SHA256计算并转换成16进制编码
     *
     * @param data     待计算的字符串
     * @param encoding 编码方式
     * @return 转换后的字符串
     */
    public static byte[] sha256X16(String data, String encoding) throws Exception {
        MessageDigest md = MessageDigest.getInstance(ALGORITHM_SHA256);
        md.reset();
        md.update(data.getBytes(encoding));
        byte[] bytes = md.digest();
        StringBuilder sha256StrBuff = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
                sha256StrBuff.append("0").append(Integer.toHexString(0xFF & bytes[i]));
            } else {
                sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
            }
        }
        return sha256StrBuff.toString().getBytes(encoding);
    }

    /**
     * 公钥转换
     *
     * @param key 公钥字符串
     * @return 公钥
     * @throws Exception 异常
     */
    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 私钥转换
     *
     * @param key 私钥字符串
     * @return 私钥
     * @throws Exception 异常
     */
    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    /**
     * 将Map存储的对象，转换为key=value&key=value的字符,value值做urlEncode
     *
     * @param requestParam
     * @param coder
     * @return
     */
    private static String getRequestParamString(Map<String, String> requestParam, String coder) {
        if (null == coder || "".equals(coder)) {
            coder = "UTF-8";
        }
        StringBuffer sf = new StringBuffer("");
        String reqstr = "";
        if (null != requestParam && 0 != requestParam.size()) {
            for (Map.Entry<String, String> en : requestParam.entrySet()) {
                try {
                    sf.append(en.getKey()
                            + "="
                            + (null == en.getValue() || "".equals(en.getValue()) ? "" : URLEncoder
                            .encode(en.getValue(), coder)) + "&");
                } catch (UnsupportedEncodingException e) {
                    System.out.println(e.getMessage());
                    return "";
                }
            }
            reqstr = sf.substring(0, sf.length() - 1);
        }
        return reqstr;
    }

    /**
     * 获取证书序列号
     *
     * @param filePath
     * @param passwd
     * @return
     */
    public static String getCertIdFromCertFile(String filePath, String passwd) {
        List<String> aliasList = new ArrayList<>();
        String keyAlias = null;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            FileInputStream fileInputStream = new FileInputStream(filePath);

            keyStore.load(fileInputStream, passwd.toCharArray());

            fileInputStream.close();
            Enumeration list = keyStore.aliases();
            if (list.hasMoreElements()) {
                keyAlias = (String) list.nextElement();
            }
            X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
            return cert.getSerialNumber().toString();
        } catch (Exception e) {

        }
        return null;
    }

    /**
     * 获取cer证书序列号
     *
     * @param filePath
     * @return
     */
    public static String getPubCertIdFromCertFile(String filePath) throws Exception {
        File certFile = new File(filePath);
        if (certFile.exists()) {
            if (filePath.endsWith(".cer")) {
                InputStream inputStream = new FileInputStream(filePath);
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
                if (null != inputStream) {
                    inputStream.close();
                }
                return x509Certificate.getSerialNumber().toString();
            } else {
                System.out.println("不支持的证书文件类型!");
                return null;
            }
        } else {
            System.out.println("证书文件不存在!");
            return null;
        }
    }

    /**
     * 处理厦门银联请求报文
     *
     * @param filePath
     * @return
     */
    public static String genXmupClientPack(String filePath) throws Exception {
        File certFile = new File(filePath);
        if (certFile.exists()) {
            if (filePath.endsWith(".cer")) {
                InputStream inputStream = new FileInputStream(filePath);
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
                if (null != inputStream) {
                    inputStream.close();
                }
                return x509Certificate.getSerialNumber().toString();
            } else {
                System.out.println("不支持的证书文件类型!");
                return null;
            }
        } else {
            System.out.println("证书文件不存在!");
            return null;
        }
    }
}
